/*
 * Copyright 2012 Alain Pannetrat (L1L1@gmx.com)
 * 
 * This file is part of cookie-miner.
 * 
 * cookie-miner is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * cookie-miner is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with cookie-miner. If not, see http://www.gnu.org/licenses/.
 *
 * @version: $Id$
 *
 */
#include "gfx_engine.h"
#include <sys/time.h>
#include <unistd.h>
#include "log_facility.h"

#ifdef _WIN32
#include <windows.h>
#define FRAME_MILLISECONDS 40
#else
#define FRAME_MILLISECONDS 40
#endif

/********************************************************************
 * FONT 
 */

char *preload="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,;.:/!*=&\"'{}()[]-|`_\\@";

bool gfx::Font::load(const char *name, unsigned size)
{
	if (loaded()) return false;

	_font = new FTTextureFont(name);
	
	// odly if FTTextureFont() fails it does not set _font to NULL so we check Error() as well
	if (!_font || _font->Error()!=0)
	{
		_font = NULL;
		return false;
	}
	
	_font->FaceSize(size);

	// this forces the font engine to cache ascii glyphs.
	_font->Advance(preload);
	
	return true;
}

void gfx::Font::unload()
{
	if (_font) delete _font;
	_font = NULL;
}

bool gfx::Font::render_text(double x, double y, const char *txt, int len)
{
	FTPoint where(x,-y,0);

	glPushMatrix();    
	glScalef(1,-1,1);
	if (_font) _font->Render(txt,len,where);
	glPopMatrix();

	return true;
}

bool gfx::Font::extent(double& width, double& height, const char *txt, int len)
{
	if (_font)
	{
		FTBBox bbox = _font->BBox(txt,len);
		width = bbox.Upper().X() - bbox.Lower().X();
		height = bbox.Upper().Y() - bbox.Lower().Y();
		return true;
	}
	width = 0;
	height = 0;
	return false;
}

gfx::Font::~Font()
{
	unload(); 
}

/********************************************************************
 * TEXTURE 
 */

bool gfx::Texture::load(const char *fname)
{
	if (_image) return true;

	if ((_image = SDL_LoadBMP(fname))!=NULL)
	{
		if ( (_image->w & (_image->w - 1)) != 0 ) 
			logger.message(logger.LOG_WARNING,"warning: %s width is not a power of 2",fname);

		if ( (_image->h & (_image->h - 1)) != 0 )
			logger.message(logger.LOG_WARNING,"warning: %s height is not a power of 2",fname);

		logger.message(logger.LOG_DEBUG,"loaded %s with %i bpp",fname,_image->format->BytesPerPixel);

		/*
		nOfColors = _image->format->BytesPerPixel;
		if (nOfColors == 4)     // contains an alpha channel
		{
			if (_image->format->Rmask == 0x000000ff)
				texture_format = GL_RGBA;
			else
				texture_format = GL_BGRA;
		} else if (nOfColors == 3)     // no alpha channel
		{
			if (_image->format->Rmask == 0x000000ff)
				texture_format = GL_RGB;
			else
				texture_format = GL_BGR;
		} else {
			printf("warning: the image is not truecolor..  this will probably break\n");
			// this error should not go unhandled
		}
		*/
	}
	else
	{
		logger.message(logger.LOG_ERROR,"Could not load %s: %s\n", fname, SDL_GetError());
		return false;
	}
	return true;
}

bool gfx::Texture::bind()
{
	if (!_image) return false;

	glPushAttrib(GL_ENABLE_BIT);

	glEnable(GL_TEXTURE_2D);
		
	glGenTextures( 1, &_texture );

	if (glGetError()==GL_NO_ERROR)
	{
		glBindTexture( GL_TEXTURE_2D, _texture );

		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

		glTexImage2D( GL_TEXTURE_2D, 
				0, 			/* level (0) */
				GL_INTENSITY, 		/* OpenGlformat */
				_image->w, _image->h, 	/* data size */
				0, 			/* border */
				GL_LUMINANCE 		/* dataformat */, 
				GL_UNSIGNED_BYTE, 
				_image->pixels );

		_binded = true;
		glPopAttrib();
		logger.message(logger.LOG_DEBUG,"Loaded texture %u",_texture);
		return true;
	}
	_binded = false;
	glPopAttrib();	
	logger.message(logger.LOG_DEBUG,"Failed to load texture: %s",gluErrorString(glGetError()));
	return false;
}

void gfx::Texture::blit(double x1, double y1, double x2, double y2)
{
	//glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);

	glEnable(GL_TEXTURE_2D);

	//glEnable(GL_BLEND);
	//glDisable(GL_DEPTH_TEST); 

	//bloom:
	//glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	
	//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glBindTexture( GL_TEXTURE_2D, _texture );

	glBegin( GL_QUADS );
		glTexCoord2i( 0, 0 );
		glVertex2f( x1, y1);

		glTexCoord2i( 1, 0 );
		glVertex2f( x2, y1);

		glTexCoord2i( 1, 1 );
		glVertex2f( x2, y2);

		glTexCoord2i( 0, 1 );
		glVertex2f( x1, y2);
	glEnd();

	glDisable(GL_TEXTURE_2D);
	//glPopAttrib();
}

gfx::Texture::~Texture()
{
	if (_image)
	{
		SDL_FreeSurface(_image);
		glDeleteTextures(1, &_texture);
	}
}

/********************************************************************
 * ENGINE 
 */

gfx::Font gfx::Engine::SansSerifFont;

gfx::Texture gfx::Engine::RoundPoint;

//unsigned gfx::Engine::_big_bang = 0; 
bool gfx::Engine::_init_engine_core = false;
int gfx::Engine::_desktop_width = 0;
int gfx::Engine::_desktop_height = 0;


gfx::Engine::Engine():
	_main_widget(NULL),
	_last_time(0),
	_last_click(0),
	_surface(NULL),
	_fullscreen(false)
{
}


unsigned gfx::Engine::time()
{
	return SDL_GetTicks();
}

bool gfx::Engine::idle()
{
	return false;
}

bool gfx::Engine::run()
{
	SDL_Event event;
	bool running = true;
	unsigned time_cur = 0;

	if (_main_widget==NULL)
		return false;

	while (running)
	{
		time_cur = gfx::Engine::time();

		if (SDL_PollEvent(&event)) {
			switch(event.type)
			{
				case SDL_MOUSEMOTION:
					_main_widget->on_mouse_move(event);						
					break;
				case SDL_MOUSEBUTTONDOWN:
					_main_widget->on_click(event);
					if (time_cur-_last_click<500)
						_main_widget->on_double_click(event);
					_last_click = time_cur;
					break;
				case SDL_MOUSEBUTTONUP:
					_main_widget->on_release(event);
					break;
				case SDL_VIDEORESIZE:
					if (screen_width()!=event.resize.w || screen_height()!=event.resize.h)
					{
						fprintf(stderr,"resize_event %ix%i\n",event.resize.w,event.resize.h);
						screen_setup(false,event.resize.w,event.resize.h);
					}
					else
						fprintf(stderr,"No resize_event needed for %ix%i\n",event.resize.w,event.resize.h);
					break;
				case SDL_KEYDOWN:
					if (event.key.keysym.unicode == 'q' && (event.key.keysym.mod&KMOD_ALT)) 
						running = false;
					else if (event.key.keysym.sym == SDLK_F11)
					{
						if (fullscreen())
							screen_setup(false);
						else
							screen_setup(true);		
					}
					else
						_main_widget->on_key_press(event);
					break;
				case SDL_QUIT:
					running = false;
			}
		}
		else
			idle();
		
		if (time_cur-_last_time>=FRAME_MILLISECONDS)
		{
			_last_time = time_cur;

			if (_main_widget->draw())
			{
				glFlush();
				SDL_GL_SwapBuffers();
			}
		}
		else
		{
			SDL_Delay((FRAME_MILLISECONDS-(time_cur-_last_time))/2);
			//usleep((40-(time_cur-_last_time))*600);
		}
	}
	return true;
}

bool gfx::Engine::init(Widget *main_widget)
{
	const SDL_VideoInfo *info; 
	
	// 
	// INIT SDL
	//

	// FIXME: check return value of SDL_Init	
	if (!_init_engine_core) {
		SDL_Init(SDL_INIT_EVERYTHING);
		SDL_EnableUNICODE(1);
		info = SDL_GetVideoInfo();
		_desktop_width = info->current_w;
		_desktop_height = info->current_h;
		_init_engine_core = true;
		RoundPoint.load("./FILES/full-point-64x64.bmp");
	}

	_main_widget = main_widget;

	return true;
}


bool gfx::Engine::screen_setup(bool do_fullscreen, int width, int height)
{
  
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	if (do_fullscreen)
	{
		if (width==0) width = _desktop_width;
		if (height==0) height = _desktop_height;
		fprintf(stderr,"Screen size requested %ix%i\n",width,height);
		_surface = SDL_SetVideoMode(width, height, 32, SDL_OPENGL | SDL_FULLSCREEN);
	}
	else
	{
		if (width==0) width = _desktop_width/2;
		if (height==0) height = _desktop_height/2;
		fprintf(stderr,"Screen size requested %ix%i\n",width,height);
		_surface = SDL_SetVideoMode(width, height, 32, SDL_OPENGL | SDL_RESIZABLE);
	}

	if (!_surface) {
		fprintf(stderr,  "Video mode %ix%i (%s) set failed: %s.\n", width, height, do_fullscreen?"fullscreen":"windowed", SDL_GetError());
		if (width==640 && height==480) SDL_Quit(); // massive fail if backup mode fails.
		return screen_setup(do_fullscreen,640,480); // backup mode
	}

	// title should be set elsewhere
	if (!do_fullscreen) SDL_WM_SetCaption("Cookie Miner - Graphic Engine",NULL);
	
	// 
	// INIT OPENGL
	//
	
	glClearColor( 0, 0, 0, 0 );
	//glEnable( GL_TEXTURE_2D ); // Need this to display a texture
	glViewport( 0, 0, _surface->w, _surface->h );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	glOrtho( 0, width-1.0, 
		 height-1.0, 0, 
		 -100, 100 );
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	glDisable(GL_DEPTH_TEST);

	glEnable( GL_POINT_SMOOTH );	
	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

	glEnable( GL_LINE_SMOOTH );
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

	//glEnable(GL_POLYGON_SMOOTH);
	//glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glClear(GL_COLOR_BUFFER_BIT);

	// 
	// INIT FONT & TEXTURES
	//
	
	if (glIsTexture(RoundPoint.value())==GL_FALSE) 
		// we use RoundPoint texture to detect opengl reset
	{
		RoundPoint.bind();
		if (SansSerifFont.loaded()) SansSerifFont.unload();
		SansSerifFont.load("./FILES/DejaVuSans.ttf",16);
	}

	//
	// CONCLUDING 
	//

	_fullscreen = do_fullscreen;
	
	Rectangle screen_rect(0,0,width-1,height-1);
	_main_widget->position(screen_rect);
	_main_widget->resize();

	return true;
}

gfx::Engine::~Engine()
{
	// FIXME: Font should not be static but a pointer in a singleton class
	// Instead, here we need to unload the font BEFORE calling SDL_Quit...
	SansSerifFont.unload();
	// Nothing to do for openGL, fonts will be automatically 
	// destroyed in time.
	SDL_Quit();
}



